﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using daStaticAnalyser;
using daSyntaxAnalyser.SyntaxTree;
using CQLQueries.SyntaxTree;

namespace CQLQueries.Analyzers
{
    public class CQL_SA
    {
        // шаблон "одиночка"
        private static CQL_SA instance;

        /// <summary>
        /// Единственный экземпляр класса контроллера
        /// </summary>
        public static CQL_SA Instance
        {
            get
            {
                if (instance == null)
                    instance = new CQL_SA();
                return instance;
            }
        }

        String query;

        public event EventHandler<ErrorArgs> ErrorOccuredEvent;

        private bool Match(CQLTokens t)
        {
            if (CQL_LA.Instance.CurTok == t)
            {
                if (!CQL_LA.Instance.Scan(CQL_LA.Instance.CurPos, query)) return false;
                else return true;
            }
            else
            {
                ErrorSyntax("Неожиданная конструкция языка.");
                return false;
            }
        }

        private void ErrorSyntax(String message)
        {
            if (ErrorOccuredEvent != null)
                    ErrorOccuredEvent(this, new ErrorArgs(message,
                        CQL_LA.Instance.CurPos - CQL_LA.Instance.CurText.Length,
                        "Синтаксическая ошибка", CQL_LA.Instance.BeginToken, CQL_LA.Instance.EndToken));
        }

        private QueryData запрос
        {
            get
            {
                return CQL_Semantic.Instance.запрос;
            }
            set
            {
                CQL_Semantic.Instance.запрос = value;    
            }
        }

        public bool Translate(string query1)
        {
            CQL_Semantic.Instance.Запросы = new List<QueryData>();
            
            CQL_LA.Instance.CurPos = 0;
            CQL_LA.Instance.CurTok = CQLTokens.Undef;
            query = query1; 
            bool rez = Querys();
            if (rez)
            {
                bool ret = CQL_LA.Instance.CurTok == CQLTokens.Undef;
                if (!ret)
                    ErrorSyntax("Неожиданный конец запроса.");
                return ret;
            }
            return false;
        }

        private bool Querys()
        {
            if (!Match(CQLTokens.Undef)) return false;
            if (!Query()) return false;
            if (!Querys2()) return false;
            return true;
        }

        private bool Querys2()
        {
            if (CQL_LA.Instance.CurTok == CQLTokens.Select)
            {
                if (!Query()) return false;
                if (!Querys2()) return false;
                //return true;
            }
            return true;
        }

        private bool Query()
        {
            QueryData q = new QueryData();
            CQL_Semantic.Instance.Запросы.Add(q);
            запрос = q;
            запрос.Begin = CQL_LA.Instance.BeginToken;
            
            QueryNode QueryNodeTemp = new QueryNode();
            запрос.КореньДерева = QueryNodeTemp;
            Type ElementTemp;            
            if (!SelectBlock(out QueryNodeTemp.SelectBlock, out ElementTemp)) return false;
            if (CQL_LA.Instance.CurTok == CQLTokens.Where)
            {
                if (!WhereBlock(out QueryNodeTemp.WhereBlock, ElementTemp)) return false;
                запрос.End = CQL_LA.Instance.EndQuery+1;
            }
            else
            {
                if (CQL_LA.Instance.CurTok == CQLTokens.Undef) return true;
                else
                {
                    ErrorSyntax("Неожиданный конец запроса.");
                    return false;
                }
            }
            return true;
        }

        private bool SelectBlock(out SelectBlockNode Node, out Type ElementUp)
        {
            Node = null;
            ElementUp = null;    
            if (!Match(CQLTokens.Select)) return false;
            if (CQL_LA.Instance.CurTok == CQLTokens.Id)
            {
                if (!CQL_Semantic.Instance.ПроверкаSelect(CQL_LA.Instance.CurText)) return false;                
                if (!Match(CQLTokens.Id)) return false;
                ElementUp = запрос.SelectType;
                Node = new SelectBlockNode();
                Node.Id = new IdNode();
                if (CQL_LA.Instance.CurTok == CQLTokens.Distinct)
                {
                    if (!Match(CQLTokens.Distinct)) return false;
                    CQL_Interpreter.Instance.Distinct = true;
                }
            }
            else
            {
                ErrorSyntax("Неожиданная конструкция языка.");
                return false;
            }
            return true;
        }

        private bool WhereBlock(out WhereBlockNode Node, Type ElementDown)
        {
            Node = null;
            if (!Match(CQLTokens.Where)) return false;
            Node = new WhereBlockNode();
            if (!Relation(out Node.Relation, ElementDown)) return false;
            return true;
        }

        private bool Relation(out RelationNode Node, Type ElementDown)
        {
            Node = null;
            Node = new RelationNode();
            if (!Relation2(out Node.Relation2, ElementDown)) return false;
            if (!Relation1(out Node.Relation1, ElementDown)) return false;
            return true;
        }

        private bool Relation1(out Relation1Node Node, Type ElementDown)
        {
            Node = null;
            if (CQL_LA.Instance.CurTok == CQLTokens.Or)
            {                
                if (!Match(CQLTokens.Or)) return false;
                Node = new Relation1Node();
                if (!Relation(out Node.Relation, ElementDown)) return false;
            }
            return true;
        }

        private bool Relation2(out Relation2Node Node, Type ElementDown)
        {
            Node = null;
            Node = new Relation2Node();
            if (!Relation4(out Node.Relation4, ElementDown)) return false;
            if (!Relation3(out Node.Relation3, ElementDown)) return false;
            return true;
        }

        private bool Relation3(out Relation3Node Node, Type ElementDown)
        {
            Node = null;
            if (CQL_LA.Instance.CurTok == CQLTokens.And)
            {
                if (!Match(CQLTokens.And)) return false;
                Node = new Relation3Node();
                if (!Relation2(out Node.Relation2, ElementDown)) return false;
            }
            return true;
        }

        private CQLTokens GetLBr()
        {
            int SavePos = CQL_LA.Instance.CurPos;
            int Count = 1;
            while ((CQL_LA.Instance.CurTok != CQLTokens.Undef)&&(Count!=0))
            {
                Match(CQL_LA.Instance.CurTok);
                switch (CQL_LA.Instance.CurTok)
                {
                    case CQLTokens.LBr: Count++; break;
                    case CQLTokens.RBr: Count--; break;
                }
            }

            while(CQL_LA.Instance.CurTok == CQLTokens.RBr) Match(CQL_LA.Instance.CurTok);

            CQLTokens tok;
            switch (CQL_LA.Instance.CurTok)
            {
                case CQLTokens.And:
                case CQLTokens.Or:
                case CQLTokens.Undef:
                case CQLTokens.Select:
                    tok = CQLTokens.LBrLog;
                    break;
                default:
                    tok = CQLTokens.LBrExp;
                    break;

            }
            CQL_LA.Instance.CurPos = SavePos;
            CQL_LA.Instance.CurTok = CQLTokens.LBr;
            return tok;
        }

        private bool Relation4_1(out Relation4Node Node, Type ElementDown)
        {
            Node = null;
            Node = new Relation4Node();
            Type LeftType;
            if (!Expression(out Node.Expression1, ElementDown, out LeftType)) return false;
            Node.RelOp = CQL_LA.Instance.CurTok;
            CQLTokens Rel; 
            switch (CQL_LA.Instance.CurTok)
            {
                case CQLTokens.Not:
                    Rel = CQL_LA.Instance.CurTok;
                    if (!Match(CQL_LA.Instance.CurTok)) return false;
                    if (!Match(CQLTokens.Like)) return false;
                    break;
                case CQLTokens.Like:
                case CQLTokens.More:
                case CQLTokens.MoreOrEqual:
                case CQLTokens.Less:
                case CQLTokens.LessOrEqual:
                case CQLTokens.Equal:
                case CQLTokens.NotEqual:
                    Rel = CQL_LA.Instance.CurTok;
                    if (!Match(CQL_LA.Instance.CurTok)) return false;
                    break;
                default:
                    return false;
            }

            Type RightType;
            if (!Expression(out Node.Expression2, ElementDown, out RightType)) return false;
            if (!CQL_Semantic.Instance.TestTypeOfValue(Rel, LeftType, RightType)) return false;
            return true;        
        }
        private bool Relation4(out Relation4Node Node, Type ElementDown)
        {
            Node = null;

            switch (CQL_LA.Instance.CurTok)
            {
                case CQLTokens.LBr:
                    if (GetLBr() == CQLTokens.LBrLog)
                    {
                        if (!Match(CQL_LA.Instance.CurTok)) return false;
                        if (CQL_LA.Instance.CurTok == CQLTokens.LBr)
                        {
                            Node = new Relation4Node();
                            if (!Relation(out Node.Relation, ElementDown)) return false;
                        }
                        else
                        {
                            if (!Relation4_1(out Node, ElementDown)) return false;
                        }
                        if (!Match(CQLTokens.RBr)) return false;
                    }
                    else
                    {
                        if (!Relation4_1(out Node, ElementDown)) return false;
                    }
                    break;
                case CQLTokens.Id:
                    if (!Relation4_1(out Node, ElementDown)) return false;
                    break;
                default:
                    ErrorSyntax("Неожиданный конец запроса.");
                    return false;
            }
            return true;
        }

        private bool Expression(out ExpressionNode Node, Type ElementDown, out Type TypeOfValue)
        {
            Node = null;
            Node = new ExpressionNode();
            Type TypeOfValue1 = null;
            if (!Term(out Node.Term, ElementDown, out TypeOfValue1))
            {
                TypeOfValue = null;
                return false;
            }
            if (!Expression2(out Node.Expression2, ElementDown, TypeOfValue1))
            {
                TypeOfValue = null;
                return false;
            }
            TypeOfValue = TypeOfValue1;
            return true;
        }

        private bool Expression2(out Expression2Node Node, Type ElementDown, Type TypeOfValueLeft)
        {
            Node = null;            
            switch (CQL_LA.Instance.CurTok)
            {
                case CQLTokens.Add:
                case CQLTokens.Sub:
                    CQLTokens tok = CQL_LA.Instance.CurTok;
                    Node = new Expression2Node(CQL_LA.Instance.CurTok);
                    if (!Match(CQL_LA.Instance.CurTok)) return false;
                    Type TypeOfValueRight = null;
                    if (!Expression(out Node.Expression, ElementDown, out TypeOfValueRight)) return false;
                    if (!CQL_Semantic.Instance.TestTypeOfValue(tok, TypeOfValueLeft, TypeOfValueRight)) return false;
                    break;
            }
            return true;
        }

        private bool Term(out TermNode Node, Type ElementDown, out Type TypeOfValue)
        {
            Node = null;
            Node = new TermNode();
            Type TypeOfValue1 = null;
            if (!Factor(out Node.Factor, ElementDown, out TypeOfValue1))
            {
                TypeOfValue = null;
                return false;
            }
            if (!Term2(out Node.Term2, ElementDown, TypeOfValue1))
            {
                TypeOfValue = null;
                return false;
            }
            TypeOfValue = TypeOfValue1;
            return true;
        }

        private bool Term2(out Term2Node Node, Type ElementDown, Type TypeOfValueLeft)
        {
            Node = null;
            switch (CQL_LA.Instance.CurTok)
            {
                case CQLTokens.Mod:
                case CQLTokens.Mul:
                case CQLTokens.Div:
                    CQLTokens tok = CQL_LA.Instance.CurTok;
                    Node = new Term2Node(CQL_LA.Instance.CurTok);
                    if (!Match(CQL_LA.Instance.CurTok)) return false;
                    Type TypeOfValueRight = null;
                    if (!Term(out Node.Term, ElementDown, out TypeOfValueRight)) return false;
                    if (!CQL_Semantic.Instance.TestTypeOfValue(tok, TypeOfValueLeft, TypeOfValueRight)) return false;
                    break;
            }
            return true;
        }

        private bool Factor(out FactorNode Node, Type ElementDown, out Type TypeOfValue)
        {
            Node = null;
            TypeOfValue = null;
            switch (CQL_LA.Instance.CurTok)
            {
                case CQLTokens.LBr:
                    {
                        if(!Match(CQLTokens.LBr)) return false;
                        Node = new FactorNode();
                        if (!Expression(out Node.Expression, ElementDown, out TypeOfValue)) return false;
                        if(!Match(CQLTokens.RBr)) return false;
                        break;
                    }
                case CQLTokens.Sub:
                    {
                        if (!Match(CQLTokens.Sub)) return false;
                        Node = new FactorNode();
                        if (!Factor(out Node.Factor, ElementDown, out TypeOfValue)) return false;                      
                        break;
                    }
                case CQLTokens.Const:
                    if (CQL_LA.Instance.CurText[0] == '"')
                    {
                        TypeOfValue = typeof(String);
                    }
                    else
                    {
                        TypeOfValue = typeof(Double);
                    }
                    Node = new FactorNode();
                    Node.Const = new ConstNode(CQL_LA.Instance.CurText);
                    if (!Match(CQLTokens.Const)) return false;
                    break;
                case CQLTokens.Id:
                    Type LeftOp = null;
                    if (!CQL_Semantic.Instance.ПроверкаWhereПодтип(CQL_LA.Instance.CurText, запрос.SelectType, out LeftOp)) return false;
                    if (LeftOp.Name == "String")
                    {
                        TypeOfValue = typeof(String);
                    }
                    else
                    {
                        TypeOfValue = typeof(Double);
                    }
                    Node = new FactorNode();
                    Node.Id = new IdNode(CQL_LA.Instance.CurText);
                    if (!Match(CQLTokens.Id)) return false;
                    break;
                default:
                    ErrorSyntax("Неожиданная конструкция языка.");
                    return false;
            }
            return true;
        }
    }
}
